
package com.studiofortress.sf.audio;

import com.studiofortress.sf.util.Repeater;
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

/**
 *
 * @author Joseph Lenton - JosephLenton@StudioFortress.com
 */
class InnerAudioPlayer extends Repeater
{
    private static final int DEFAULT_VOLUME = 90;
    private static final int DEFAULT_CHUNK_SIZE = 512*4;

    private static SourceDataLine newSourceDataLine(AudioFormat format)
    {
        try {
            final DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
            final SourceDataLine newDataLine = (SourceDataLine) AudioSystem.getLine(info);
            
            newDataLine.open( format );

            return newDataLine;
        } catch (LineUnavailableException ex) {
            throw new RuntimeException( ex );
        }
    }
    
    private boolean paused;
    private boolean playing;
    private boolean repeat;
    private int volume;

    private final AudioStreamFactory factory;
    private final byte[] tempBuffer;
    
    private AudioInputStream stream;
    private SourceDataLine sourceDataLine;
    
    public InnerAudioPlayer(AudioStreamFactory factory)
    {
        this.factory = factory;
        this.tempBuffer = new byte[DEFAULT_CHUNK_SIZE];
        
        this.stream = null;
        this.sourceDataLine = null;

        this.playing = false;
        this.paused = false;
        this.repeat = false;
        this.volume = DEFAULT_VOLUME;
    }

    public boolean isPaused()
    {
        return paused;
    }

    public boolean isPlaying()
    {
        return playing;
    }
    
    public synchronized void pause()
    {
        if ( isPlaying() ) {
            paused = true;
            sourceDataLine.stop();
        }
    }
    
    public synchronized void play()
    {
        if ( !isPlaying() ) {
            this.notify();

            if (sourceDataLine == null) {
                stream = factory.newStream();
                sourceDataLine = newSourceDataLine( stream.getFormat() );
                setVolume( volume );
            }

            sourceDataLine.start();

            paused = false;
            playing = true;
        }
    }
    
    public synchronized void stopPlaying()
    {
        if ( isPlaying() || isPaused() ) {
            sourceDataLine.drain();
            sourceDataLine.close();
            sourceDataLine = null;
            
            try {
                stream.close();
                stream = null;
            } catch (IOException ex) { }

            paused = false;
            playing = false;
        }
    }
    
    public void setAudioRepeating(boolean repeat)
    {
        this.repeat = repeat;
    }

    public boolean isAudioRepeating()
    {
        return repeat;
    }
    
    public synchronized void setVolume(int volume)
    {
        this.volume = volume;
        final FloatControl volumeControl = getVolumeControl();

        if (volumeControl != null) {
            final float minimum = volumeControl.getMinimum();
            final float diff = volumeControl.getMaximum() - minimum;
            
            volume = Math.round(((volume / 100.0f) * diff) + minimum);
            volumeControl.setValue( volume );
        }
    }

    public int getVolume()
    {
        return volume;
    }

    /**
     * @return The current volume this is really playing at, according to the data line.
     */
    private int getDataLineVolume()
    {
        final FloatControl volumeControl = getVolumeControl();
        final float minimum = volumeControl.getMinimum();
        final float diff = volumeControl.getMaximum() - minimum;

        return Math.round(((volumeControl.getValue() - minimum) / diff) * 100.0f);
    }

    /**
     *
     * @return A FloatControl that can be used for volume usage.
     */
    private FloatControl getVolumeControl()
    {
        if (sourceDataLine != null) {
            if (sourceDataLine.isControlSupported(FloatControl.Type.VOLUME)) {
                return (FloatControl)sourceDataLine.getControl(FloatControl.Type.VOLUME);
            } else if (sourceDataLine.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
                return (FloatControl)sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN);
            }
        }

        return null;
    }
    
    @Override
    public synchronized void repeat()
    {
        try {
            if (isPlaying()) {
                final int read = stream.read( tempBuffer );
                if (read < 0) {
                    stopPlaying();
                    if (isAudioRepeating()) {
                        play();
                    }
                } else if (read > 0) {
                    sourceDataLine.write( tempBuffer, 0, read );
                }
            } else {
                this.wait();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}
